#include <SPI.h>
#include "mongoose.h"

#define LED_PIN LED_BUILTIN  // LED pin
#define SS_PIN 17            // Slave select pin for the W5500 module

struct mg_tcpip_spi spi = {
    NULL,  // SPI metadata
    [](void *) { digitalWrite(SS_PIN, LOW); SPI.beginTransaction(SPISettings()); },
    [](void *) { digitalWrite(SS_PIN, HIGH); SPI.endTransaction(); },
    [](void *, uint8_t c) { return SPI.transfer(c); }, // Execute transaction
};
struct mg_mgr mgr;                                     // Mongoose event manager
struct mg_tcpip_if mif = {.mac = {2, 0, 1, 2, 3, 5}};  // Network interface

// Used by Mongoose for time tracking
uint64_t mg_millis(void) {
  return millis();
}

// Used by Mongoose to generate random data
bool mg_random(void *buf, size_t len) {  // For TLS
  uint8_t *p = (uint8_t *) buf;
  while (len--) *p++ = (unsigned char) (rand() & 255);
  return true;
}

// Crude function to get available RAM, for quick profiling
extern "C" char *sbrk(int);
extern char *__brkval;
int getFreeRAM() {
  char top;
#ifdef __arm__
  return &top - (char *) sbrk(0);
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif
}

static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_HTTP_MSG) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    if (mg_match(hm->uri, mg_str("/api/led/on"), NULL)) {
      digitalWrite(LED_PIN, HIGH);
      mg_http_reply(c, 200, "", "{%m: %d}\n", MG_ESC("led"), digitalRead(LED_PIN));
    } else if (mg_match(hm->uri, mg_str("/api/led/off"), NULL)) {
      digitalWrite(LED_PIN, LOW);
      mg_http_reply(c, 200, "", "{%m: %d}\n", MG_ESC("led"), digitalRead(LED_PIN));
    } else {
      mg_http_reply(c, 200, "", "ok, free RAM: %u\n", getFreeRAM());
    }
  }
  // Initialise TLS if we're a TLS listener
  if (c->is_tls && ev == MG_EV_ACCEPT) {
    struct mg_tls_opts opts;
    memset(&opts, 0, sizeof(opts));

    // Generated by https://mongoose.ws/tls/
#define TLS_CERT \
  "-----BEGIN CERTIFICATE-----\n" \
  "MIIBkzCCATqgAwIBAgIEZ3E9lzAKBggqhkjOPQQDAjATMREwDwYDVQQDEwhNb25n\n" \
  "b29zZTAeFw0yNDEyMjkxMjE2MjNaFw0zNDEyMjcxMjE2MjNaMBQxEjAQBgNVBAMT\n" \
  "CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHxxuByu5K2k7DYq\n" \
  "b3eQsHb3VY5NWglWun7axh69OWF7V9OUUyON1y8ISirL0Gj5ZPOogIgrB9iOcz6K\n" \
  "q9n8NQOjezB5MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAM\n" \
  "BgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFGn8GpPWaM0JC7U4zlg+bCljSjLoMCMG\n" \
  "A1UdEQQcMBqCCWxvY2FsaG9zdIINMTkyLjE2OC4wLjEwMDAKBggqhkjOPQQDAgNH\n" \
  "ADBEAiAr1fO3QLLm+vjYarctNI+gfnxxB4edRTRmcFWHmBS8oQIgY5lkq2JCwATG\n" \
  "YsVQUmS+2Tbc7ij7lkduXO42pvr/0fw=\n" \
  "-----END CERTIFICATE-----"

  #define TLS_KEY \
  "-----BEGIN EC PRIVATE KEY-----\n" \
  "MHcCAQEEIHKu84eEw9dX8nez82g3F94OsZJ14LVbAN+OBW+++V2poAoGCCqGSM49\n" \
  "AwEHoUQDQgAEfHG4HK7kraTsNipvd5CwdvdVjk1aCVa6ftrGHr05YXtX05RTI43X\n" \
  "LwhKKsvQaPlk86iAiCsH2I5zPoqr2fw1Aw==\n" \
  "-----END EC PRIVATE KEY-----"

    opts.cert = mg_str(TLS_CERT);
    opts.key = mg_str(TLS_KEY);

    mg_tls_init(c, &opts);
  }
}

void setup() {
  Serial.begin(115200);       // Initialise serial
  while (!Serial) delay(50);  // for debug output

  SPI.begin();               // Iniitialise SPI
  pinMode(SS_PIN, OUTPUT);   // to communicate with W5500 Ethernet module
  pinMode(LED_PIN, OUTPUT);  // Initialise LED

  mg_mgr_init(&mgr);        // Initialise Mongoose event manager
  mg_log_set(MG_LL_DEBUG);  // Set debug log level
  mg_log_set_fn([](char ch, void *) { Serial.print(ch); }, NULL);  // Log serial
  mif.driver = &mg_tcpip_driver_w5500;  // Use W5500 built-in driver
  mif.driver_data = &spi;               // Pass SPI interface to W5500 driver
  mg_tcpip_init(&mgr, &mif);            // Initialise built-in TCP/IP stack

  // Setup HTTP & HTTPS listeners. Respond "ok" on any HTTP request
  mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL);
  mg_http_listen(&mgr, "https://0.0.0.0:443", http_ev_handler, NULL);
}

void loop() {
  mg_mgr_poll(&mgr, 1);
}
